package com.atguigu.tingshu.account.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

    @Autowired
    private UserAccountMapper userAccountMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private UserAccountDetailMapper userAccountDetailMapper;

    @Override
    public BigDecimal getAvailableAmount(Long userId) {
        UserAccount userAccount = lambdaQuery().eq(UserAccount::getUserId, userId).one();
        return userAccount.getAvailableAmount();
    }

    // OpenFeign可能会多次调用
    // 使用OpenFeign的时候一定要注意幂等性保证
    // OpenFeign有重试机制。默认没有使用，但是程序员可以手动打开重试机制
    // 底层提供的重试器会重试5次，每次重试的间隔不一样。

    // openFeign在第一次调用的时候 出现了网络抖动（1min）
    // 开启了重试，30s OpenFeign进行第一次重试


    // 场景：1号用户 可用余额100块钱
    // 第一次调用1号用户余额充足，扣减50 剩下50

    // OpenFeign对超时进行重试 不会对业务执行期间出现的异常进行重试。
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<AccountLockResultVo> checkAmountAndLockAmount(AccountLockVo accountLockVo) {
        // 1.获取变量
        Long userId = accountLockVo.getUserId();
        BigDecimal amount = accountLockVo.getAmount();
        String content = accountLockVo.getContent();
        String orderNo = accountLockVo.getOrderNo();
        String dataKey = "user:account:amount:" + orderNo;
        String lockKey = "user:account:amount:lock:" + orderNo;

        // 2.添加分布式锁
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, "1");
        if (!aBoolean) {
            return Result.build(null, ResultCodeEnum.ACCOUNT_LOCK_ERROR);
        }

        // 3.将判断余额是否充足和修改余额作为原子操作利用CAS思想（比较并交换）
        // 1.查询余额是否充足
        // 伪SQL
        // select available_account from user_account where user_id = #{userId} and available_account >= #{amount}
        // 2.1.如果余额不充足，抛出非200的状态码
        // 2.2.如果余额充足 锁定余额
        // 伪SQL
        // update user_account set lock_amount = lock_amount + #{amount},available_account = available_account - #{amount} where user_id = #{userId}

        // 3.将判断余额是否充足和修改余额作为原子操作利用CAS思想（比较并交换）
        try {
            String result = redisTemplate.opsForValue().get(dataKey);
            if (!StringUtils.isEmpty(result)) {
                return Result.build(JSONObject.parseObject(result, AccountLockResultVo.class), ResultCodeEnum.SUCCESS);
            }

            int count = userAccountMapper.checkAndLockAmount(userId, amount);
            // 3.1 如果锁定失败（钱不够）
            if (count == 0) {
                return Result.build(null, ResultCodeEnum.ACCOUNT_LOCK_ERROR);
            }
            // 3.2 锁定成功 构建AccountLockResultVo
            AccountLockResultVo accountLockResultVo = new AccountLockResultVo();
            accountLockResultVo.setUserId(userId);
            accountLockResultVo.setAmount(amount);
            accountLockResultVo.setContent(content);

            // 3.3.记录用户账户的明细（为了以后能知道用户到底花钱干了什么事情）
            this.log(userId, "锁定: "+content, amount, orderNo,"1202");

            // 3.4.将锁定结果对象存储到Redis中（方便解锁以及真正扣除余额）
            redisTemplate.opsForValue().set(dataKey, JSONObject.toJSONString(accountLockResultVo));

            // 3.5.返回账户锁定对象
            return Result.ok(accountLockResultVo);

        } catch (Exception e) {
            // OpenFeign业务执行期间出现了异常 让openFeign进行重试(TODO)
            throw new GuiguException(400, "服务内部处理数据出现了异常");
        } finally {
            redisTemplate.delete(lockKey);
        }


    }
    // 场景：本质 1号用户 锁50元      数据库 1:50
    // SQL1：检查余额
    // select available_account from user_account where user_id = #{userId} and available_account >= #{amount}
    // SQL2：锁定余额
    // update user_account set lock_amount = lock_amount + #{amount},available_account = available_account - #{amount} where user_id = #{userId}
    // 一条：CAS思想的SQL层面落地
    // update user_account set lock_amount = lock_amount + #{amount},available_account = available_account - #{amount} where user_id = #{userId} and available_account >= #{amount}

    // 多个人修改：---修改之前先来问一下 当前这个资源是不是符合预期的 如果是 则修改 如果不是别修改（或者告诉修改失败）
    // CAS(比较并交换) compare And Swap

    @Override
    public void log(Long userId, String content, BigDecimal amount, String orderNo,String tradeType) {
        UserAccountDetail userAccountDetail = new UserAccountDetail();
        userAccountDetail.setUserId(userId);
        userAccountDetail.setTitle(content);
        userAccountDetail.setTradeType("1202");
        userAccountDetail.setAmount(amount);
        userAccountDetail.setOrderNo(orderNo);
        userAccountDetailMapper.insert(userAccountDetail);
    }

}
